package pcloud.client.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.LinkedList;
import java.util.List;

public class FileSystemDB<V> implements TreeDB<V>
{
	public FileSystemDB()
	{
		fileRoot = GlobalConfig.getInstance().getProperty("localDBFolder");
		if (fileRoot==null) {
			fileRoot = "D:/pcloud/filesystemdb/";
		}
		File rootFolder = new File(fileRoot);
		if (!rootFolder.exists()) {
			rootFolder.mkdirs();
		}
		logger.info("localDBFolder: "+ fileRoot);
	}

	private static String fileRoot;

	@Override
	public boolean contains(String path)
	{
		logger.info("FilesystemDB, contains path: "+path);
		Record record = findRecord(path);
		return record==null ? false : true;
	}

	@Override
	public V get(String path)
	{
		Record record = findRecord(path);
		logger.info("FilesystemDB, get path: "+path+ " \r\nreturn: "+record);
		return record==null ? null : readFromFile(record.getRecordFile());
	}

	@Override
	public boolean delete(String path)
	{
		Record record = findRecord(path);
		if (record!=null) {
			record.getRecordFile().delete();
			if (record.isFolder()) {
				record.getFolderFile().delete();
			}
			return true;
		}
		return false;
	}

	private Record findRecord(String path)
	{
		String fullPath = fileRoot + convertPath(path);
		logger.info("FilesystemDB, findRecord fullPath: "+fullPath);
		File file = new File(fullPath);
		if (file.exists()) {
//			logger.info("FilesystemDB, file exists!");
			if (!file.isDirectory()) {
				return new Record(file);
			}
//			logger.info("FilesystemDB, isFolder!");
			String folderPath = filePathForFolderValue(fullPath);
			File folderRecordFile = new File(folderPath);
//			logger.info("FilesystemDB, folderFilePath:"+folderPath);
			if (folderRecordFile.exists()) {
				return new Record(folderRecordFile, file);
			}
//			logger.info("FilesystemDB, folder description file doesn't exists!");
		}
		return null;
	}

	private class Record
	{
		private File recordFile;
		private boolean isFolder;
		private File folderFile;

		public Record(File recordFile)
		{
			this.recordFile = recordFile;
			this.isFolder = false;
			this.folderFile = null;
		}

		public Record(File recordFile, File folderFile)
		{
			this.recordFile = recordFile;
			this.isFolder = true;
			this.folderFile = folderFile;
		}

		public File getRecordFile()
		{
			return recordFile;
		}

		public File getFolderFile()
		{
			return folderFile;
		}

		public boolean isFolder()
		{
			return isFolder;
		}
	}

	@Override
	public List<V> getChild(String path)
	{
		String fullPath = fileRoot + convertPath(path);
		File file = new File(fullPath);
		List<V> childNodes = new LinkedList<V>();
		if (file.exists()) {
			if (file.isDirectory()) {
				File[] childFiles = file.listFiles();
				for (File childFile : childFiles) {
					if (childFile.isDirectory()) {
						// real directory/folder should be omitted here, for
						// there is a file to describe this folder.
					} else {
						V value = readFromFile(childFile);
						childNodes.add(value);
					}
				}
			}
		}
		return childNodes;
	}

	@Override
	public V put(String path, V value, boolean isFolder)
	{
		String fullPath = fileRoot + convertPath(path);
		File file = new File(fullPath);
		if (!file.exists()) {
			checkAndMakeFolder(fullPath);
			if (isFolder) {
				file.mkdir();
			}
		}
		if (isFolder) {
			String folderPath = filePathForFolderValue(fullPath);
			file = new File(folderPath);
		}

		writeToFile(file, value);
		return value;
	}

	private void checkAndMakeFolder(String fullPath)
	{
		logger.info("FileSystemDB.checkAndMakeFolder: [" + fullPath + "]");
		FilePathUtil pathUtil = new FilePathUtil(fullPath);
		String folder = pathUtil.parentFolder;
		if (folder == null) {
			return;
		} else {
			File folderFile = new File(folder);
			if (folderFile.exists()) {
				return;
			} else {
//				checkAndMakeFolder(folder);
				folderFile.mkdirs();
			}
		}
	}

	private String filePathForFolderValue(String path)
	{
		FilePathUtil pathUtil = new FilePathUtil(path);
		String fileName = "~pcloudfolder~" + pathUtil.selfName;
		String filePath;
		if (pathUtil.parentFolder == null) {
			filePath = fileName;
		} else {
			filePath = pathUtil.parentFolder + FilePathUtil.splitter + fileName;
		}
		return filePath;
	}

	private void writeToFile(File file, V value)
	{
		try {
			ObjectOutputStream outputStream = new ObjectOutputStream(
					new FileOutputStream(file));
			outputStream.writeObject(value);
			outputStream.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@SuppressWarnings( { "unchecked" })
	private V readFromFile(File file)
	{
		if (file == null) {
			return null;
		}
		try {
			ObjectInputStream inputStream = new ObjectInputStream(
					new FileInputStream(file));
			V value = (V) inputStream.readObject();
			inputStream.close();
			return value;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	private class FilePathUtil
	{
		String parentFolder;
		String selfName;
		static final char splitter = '/';

		public FilePathUtil(String path)
		{
			splitFileNameFromPath(path);
		}

		private void splitFileNameFromPath(String path)
		{
			int lastIndexOfBacklash = path.lastIndexOf(splitter);
			if (lastIndexOfBacklash < 0) {
				this.parentFolder = null;
				this.selfName = path;
				return;
			}
			this.parentFolder = path.substring(0, lastIndexOfBacklash);
			this.selfName = path.substring(lastIndexOfBacklash + 1);
		}
	}

	private String convertPath(String path)
	{
		return path.replace(':', '~').replace('\\', '/');
	}

}
